package org.dftproject.genesis.core.tasks;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dftproject.genesis.core.Activator;
import org.eclipse.core.runtime.Platform;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.util.UtfOps;

public class LocalTaskRepository implements ITaskRepository {

	private static final Log log = LogFactory.getLog(LocalTaskRepository.class);

	private Environment environment;

	public LocalTaskRepository() {
		File cacheFile = new File(getPath());
		cacheFile.mkdirs();

		try {
			EnvironmentConfig environmentConfig = new EnvironmentConfig();
			environmentConfig.setAllowCreate(true);
			environment = new Environment(cacheFile, environmentConfig);

			Activator.getDefault().getBundle().getBundleContext().addBundleListener(new SynchronousBundleListener() {

				public void bundleChanged(BundleEvent event) {
					if (event.getType() == BundleEvent.STOPPING)
						close();
				}

			});
		} catch (DatabaseException e) {
			log.error("Failed to open local task repository environment", e);
		}
	}

	public String getPath() {
		return Platform.getInstanceLocation().getURL().getPath().substring(1) + "tasks";
	}

	public void close() {
		if (environment != null) {
			log.info("Closing local task repository...");

			try {
				environment.close();
				environment = null;
			} catch (DatabaseException e) {
				log.error("Failed to close local task repository environment", e);
			}
		}
	}

	protected Database getRepository() {
		try {
			DatabaseConfig repositoryConfig = new DatabaseConfig();
			repositoryConfig.setAllowCreate(true);
			return environment.openDatabase(null, "repository", repositoryConfig);
		} catch (DatabaseException e) {
			log.error("Failed to open local task repository database", e);
		}

		return null;
	}
	
	public void addTask(ITask task) {
		// TODO Accept any task, not just local tasks
		if (!(task instanceof LocalTask))
			throw new IllegalArgumentException("task must be a local task");
		
		Properties properties = new Properties();
		properties.setProperty("id", task.getId());
		properties.setProperty("label", task.getLabel());
		
		Database database = null;

		try {
			database = getRepository();
			if (database != null) {				
				DatabaseEntry key = new DatabaseEntry(UtfOps.stringToBytes(task.getId()));
				
				ByteArrayOutputStream os = new ByteArrayOutputStream();
				properties.store(os, null);
				DatabaseEntry data = new DatabaseEntry(os.toByteArray());
				
				database.put(null, key, data);
			}
		} catch (Exception e) {
			log.error("Failed to add task '" + task.getId() + "' to local task repository", e);
		} finally {
			try {
				if (database != null)
					database.close();
			} catch (DatabaseException e) {
			}
		}
	}

	public void updateTask(ITask task) {
		// TODO Accept any task, not just local tasks
		if (!(task instanceof LocalTask))
			throw new IllegalArgumentException("task must be a local task");
		
		Properties properties = new Properties();
		properties.setProperty("id", task.getId());
		properties.setProperty("label", task.getLabel());
		
		Database database = null;

		try {
			database = getRepository();
			if (database != null) {				
				DatabaseEntry key = new DatabaseEntry(UtfOps.stringToBytes(task.getId()));
				
				ByteArrayOutputStream os = new ByteArrayOutputStream();
				properties.store(os, null);
				DatabaseEntry data = new DatabaseEntry(os.toByteArray());
				
				database.put(null, key, data);
			}
		} catch (Exception e) {
			log.error("Failed to update task '" + task.getId() + "' in local task repository", e);
		} finally {
			try {
				if (database != null)
					database.close();
			} catch (DatabaseException e) {
			}
		}
	}

	public void removeTask(ITask task) {
		Database database = null;

		try {
			database = getRepository();
			if (database != null) {				
				DatabaseEntry key = new DatabaseEntry(UtfOps.stringToBytes(task.getId()));
				database.delete(null, key);
			}
		} catch (Exception e) {
			log.error("Failed to remove task '" + task.getId() + "' from local task repository", e);
		} finally {
			try {
				if (database != null)
					database.close();
			} catch (DatabaseException e) {
			}
		}
	}

	public Set<ITask> getTasks() {
		Set<ITask> tasks = new HashSet<ITask>();

		Database database = null;
		Cursor cursor = null;

		try {
			database = getRepository();
			if (database != null) {
				cursor = database.openCursor(null, CursorConfig.DEFAULT);
				if (cursor != null) {
					DatabaseEntry key = new DatabaseEntry();
					DatabaseEntry data = new DatabaseEntry();

					while (cursor.getNext(key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
						try {
							Properties properties = new Properties();
							properties.load(new ByteArrayInputStream(data.getData()));
							
							tasks.add(new LocalTask(properties));
						} catch (IOException e) {
							log.error("Failed to load task from local task repository", e);
						}
					}
				}
			}
		} catch (DatabaseException e) {
			log.error("Failed to get tasks from local task repository", e);
		} finally {
			try {
				if (cursor != null)
					cursor.close();
				if (database != null)
					database.close();
			} catch (DatabaseException e) {
			}
		}

		return tasks;
	}

	public ITask getTask(String id) {
		ITask task = null;
		
		Database database = null;
		
		try {
			database = getRepository();
			if (database != null) {
				DatabaseEntry key = new DatabaseEntry(UtfOps.stringToBytes(id));
				DatabaseEntry data = new DatabaseEntry();

				while (database.get(null, key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					try {
						Properties properties = new Properties();
						properties.load(new ByteArrayInputStream(data.getData()));
						
						task = new LocalTask(properties);
					} catch (IOException e) {
						log.error("Failed to load task '" + id + "' from local task repository", e);
					}
				}
			}
		} catch (DatabaseException e) {
			log.error("Failed to get task '" + id + "' from local task repository", e);
		} finally {
			try {
				if (database != null)
					database.close();
			} catch (DatabaseException e) {
			}
		}
		
		return task;
	}

}
